import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression, SGDRegressor
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, r2_score
import pandas as pd
import matplotlib as mpl

# 设置支持中文的字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'Microsoft YaHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题


# 生成模拟的California Housing数据集
def generate_california_housing(n_samples=1000):
    np.random.seed(320)

    # 生成特征 (8个特征)
    med_inc = np.random.lognormal(mean=1.0, sigma=0.5, size=n_samples)  # 收入中位数
    house_age = np.random.uniform(1, 50, size=n_samples)  # 房屋年龄
    ave_rooms = np.random.uniform(1, 10, size=n_samples)  # 平均房间数
    ave_bedrms = np.random.uniform(0.5, 5, size=n_samples)  # 平均卧室数
    population = np.random.poisson(lam=1000, size=n_samples)  # 人口
    ave_occup = np.random.uniform(1, 5, size=n_samples)  # 平均入住率
    latitude = np.random.uniform(32, 42, size=n_samples)  # 纬度
    longitude = np.random.uniform(-124, -114, size=n_samples)  # 经度

    # 生成目标值 - 房价中位数（单位：万美元）
    # 使用非线性关系增加复杂性
    price = (med_inc * 2.0 +
             house_age * 0.1 +
             np.log(ave_rooms) * 1.5 -
             ave_bedrms * 0.5 +
             np.sqrt(population) * 0.01 -
             ave_occup * 0.2 +
             (latitude - 36) ** 2 * 0.01 +
             (longitude + 119) ** 2 * 0.01 +
             np.random.normal(0, 1, size=n_samples))

    # 创建DataFrame
    data = pd.DataFrame({
        'MedInc': med_inc,
        'HouseAge': house_age,
        'AveRooms': ave_rooms,
        'AveBedrms': ave_bedrms,
        'Population': population,
        'AveOccup': ave_occup,
        'Latitude': latitude,
        'Longitude': longitude,
        'MedHouseVal': price
    })

    return data


# 加载模拟数据
california = generate_california_housing()
print("数据集描述:\n", california.describe())

# 准备特征和目标值
X = california.drop('MedHouseVal', axis=1).values
y = california['MedHouseVal'].values


# 实验函数
def run_experiment(test_size, model_type='linear', alpha=0.01, max_iter=1000):
    """运行回归实验"""
    # 分割数据集
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, random_state=42)

    # 标准化特征
    scaler_X = StandardScaler()
    X_train = scaler_X.fit_transform(X_train)
    X_test = scaler_X.transform(X_test)

    # 标准化目标值
    scaler_y = StandardScaler()
    y_train = scaler_y.fit_transform(y_train.reshape(-1, 1)).ravel()
    y_test = scaler_y.transform(y_test.reshape(-1, 1)).ravel()

    # 选择并训练模型
    if model_type == 'linear':
        model = LinearRegression()
    elif model_type == 'sgd':
        model = SGDRegressor(alpha=alpha, max_iter=max_iter, penalty='l2', random_state=42)

    model.fit(X_train, y_train)

    # 预测
    y_pred = model.predict(X_test)

    # 反标准化结果以便解释
    y_test_orig = scaler_y.inverse_transform(y_test.reshape(-1, 1)).ravel()
    y_pred_orig = scaler_y.inverse_transform(y_pred.reshape(-1, 1)).ravel()

    # 计算指标
    mse = mean_squared_error(y_test_orig, y_pred_orig)
    r2 = r2_score(y_test_orig, y_pred_orig)

    # 可视化：选择MedInc特征进行可视化
    plt.figure(figsize=(12, 8))

    # 绘制散点图
    plt.scatter(scaler_X.inverse_transform(X_test)[:, 0], y_test_orig,
                alpha=0.3, color='blue', label='实际房价')

    # 创建用于绘制拟合直线的数据点
    # 固定其他特征为中位数，只变化MedInc特征
    sample_point = np.median(X_train, axis=0)
    med_inc_range = np.linspace(X_test[:, 0].min(), X_test[:, 0].max(), 100)

    line_points = np.tile(sample_point, (100, 1))
    line_points[:, 0] = med_inc_range

    # 预测并反标准化
    line_pred = model.predict(line_points)
    line_pred_orig = scaler_y.inverse_transform(line_pred.reshape(-1, 1)).ravel()

    # 绘制拟合直线
    plt.plot(scaler_X.inverse_transform(line_points)[:, 0], line_pred_orig,
             'r-', linewidth=3, label='拟合直线')

    plt.title(f'加州房价预测 (测试集比例: {test_size * 100}%, 模型: {model_type.upper()})', fontsize=14)
    plt.xlabel('收入中位数 (标准化前)', fontsize=12)
    plt.ylabel('房价中位数 (万美元)', fontsize=12)
    plt.grid(alpha=0.2)
    plt.legend()
    plt.tight_layout()

    # 打印模型系数
    if model_type == 'linear':
        print("\n线性回归系数:")
        for i, col in enumerate(california.columns[:-1]):
            print(f"{col}: {model.coef_[i]:.4f}")
        print(f"截距: {model.intercept_:.4f}")

    return mse, r2


# 主函数
def main():
    # 比较不同分割比例
    print("\n" + "=" * 50)
    print("比较不同分割比例 (使用线性回归)")
    print("=" * 50)

    # 7:3 分割
    print("\n分割比例 7:3:")
    mse_70, r2_70 = run_experiment(test_size=0.3, model_type='linear')
    plt.savefig(f'california_70_linear.png', dpi=120)
    print(f"均方误差(MSE): {mse_70:.4f}")
    print(f"决定系数(R²): {r2_70:.4f}")

    # 8:2 分割
    print("\n分割比例 8:2:")
    mse_80, r2_80 = run_experiment(test_size=0.2, model_type='linear')
    plt.savefig(f'california_80_linear.png', dpi=120)
    print(f"均方误差(MSE): {mse_80:.4f}")
    print(f"决定系数(R²): {r2_80:.4f}")

    # 分析不同分割比例的结果
    print("\n分割比例比较分析:")
    print(f"8:2分割的MSE比7:3分割 {('低', '高')[mse_80 > mse_70]} {abs(mse_80 - mse_70):.4f}")
    print(f"8:2分割的R²比7:3分割 {('高', '低')[r2_80 < r2_70]} {abs(r2_80 - r2_70):.4f}")
    print("结论: 更大的训练集通常会带来更好的模型性能")

    # 参数调优比较 (使用SGDRegressor)
    print("\n" + "=" * 50)
    print("参数调优比较 (使用SGDRegressor)")
    print("=" * 50)

    # 默认参数
    print("\nSGD默认参数 (alpha=0.0001, max_iter=1000):")
    mse_default, r2_default = run_experiment(test_size=0.2, model_type='sgd', alpha=0.0001)
    plt.savefig(f'california_sgd_default.png', dpi=120)
    print(f"均方误差(MSE): {mse_default:.4f}")
    print(f"决定系数(R²): {r2_default:.4f}")

    # 调整alpha参数 (正则化强度)
    print("\n调整alpha参数 (alpha=0.01):")
    mse_alpha, r2_alpha = run_experiment(test_size=0.2, model_type='sgd', alpha=0.01)
    plt.savefig(f'california_sgd_alpha.png', dpi=120)
    print(f"均方误差(MSE): {mse_alpha:.4f}")
    print(f"决定系数(R²): {r2_alpha:.4f}")

    # 调整max_iter参数 (迭代次数)
    print("\n调整max_iter参数 (max_iter=5000):")
    mse_iter, r2_iter = run_experiment(test_size=0.2, model_type='sgd', alpha=0.01, max_iter=5000)
    plt.savefig(f'california_sgd_iter.png', dpi=120)
    print(f"均方误差(MSE): {mse_iter:.4f}")
    print(f"决定系数(R²): {r2_iter:.4f}")

    # 分析参数调优结果
    print("\n参数调优分析:")
    print(f"增加alpha(MSE变化): {mse_default:.4f} -> {mse_alpha:.4f} ({'提升' if mse_alpha < mse_default else '下降'})")
    print(f"增加max_iter(MSE变化): {mse_alpha:.4f} -> {mse_iter:.4f} ({'提升' if mse_iter < mse_alpha else '下降'})")
    print("结论: 适当增加正则化强度(alpha)可以防止过拟合，增加迭代次数可以提升模型性能")

    plt.show()


if __name__ == "__main__":
    main()